//
//  ZGAudioFingerPrintTool.m
//  ZGAudioDetective
//
//  Created by 周刚涛 on 2021/9/15.
//  提取音轨 --> 音轨转化成NSData --> 削减音频 --> 获取采样值 --> 绘制图形

#import <AVFoundation/AVFoundation.h>
#import "ZGAudioFingerPrintTool.h"

@implementation ZGAudioFingerPrintTool


//提取音轨
/*
 *iOS开发中，AVFoundation提供了大量简单易用的API可满足一般需求的多媒体音视频处理，
 *本次提取音轨主要使用AVAsset和AVAssetTrack两个类。
*/

//上面获得的track就是提取出来的视频音轨，接下来要对音轨进行转化，转化的目的是为了量化音频的大小，方便接下来图形的绘制。

//音轨转化成NSData
/**
 在之前获得到track之后，对track的数据进行读取，AVAssetReader可以监听读取的状态，
 结合AVAssetReaderTrackOutput可以获得到每一个CMSampleBufferRef，
 最后，将每一个CMSampleBufferRef转化成NSData存储起来就完成了音轨转化为NSData的过程。
 */



- (void)getRecorderDataFromURL:(NSURL *)url WithBlock:(RecorderDataDone)callBlk {
    AVAsset *asset = [AVAsset assetWithURL:url]; //获取文件
    //从媒体中得到声音轨道
    AVAssetTrack *track = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject];
    //用于保存音频数据
    NSMutableData *data = [[NSMutableData alloc] init];
    
    //读取配置
    NSDictionary *dic = @{AVFormatIDKey :@(kAudioFormatLinearPCM),
                          AVLinearPCMIsBigEndianKey:@NO,    // 小端存储
                          AVLinearPCMIsFloatKey:@NO,    //采样信号是整数
                          AVLinearPCMBitDepthKey :@(16)  //采样位数默认 16
                        };

    NSError *error;
    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:&error]; //创建读取
    if (!reader) {
        NSLog(@"%@",[error localizedDescription]);
    }
    //读取输出，在相应的轨道上输出对应格式的数据
    AVAssetReaderTrackOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:track outputSettings:dic];
       
    //赋给读取并开启读取
    [reader addOutput:output];
    [reader startReading];
       
    //读取是一个持续的过程，每次只读取后面对应的大小的数据
    while (reader.status == AVAssetReaderStatusReading) {
        CMSampleBufferRef sampleBuffer = [output copyNextSampleBuffer]; //读取到数据
        if (sampleBuffer) {
            CMBlockBufferRef blockBUfferRef = CMSampleBufferGetDataBuffer(sampleBuffer);//取出数据
            //返回一个大小，size_t针对不同的平台有不同的实现，扩展性更好
            size_t length = CMBlockBufferGetDataLength(blockBUfferRef);
               
            SInt16 sampleBytes[length];
            CMBlockBufferCopyDataBytes(blockBUfferRef, 0, length, sampleBytes); //将数据放入数组
            [data appendBytes:sampleBytes length:length]; //将数据附加到data中
            CMSampleBufferInvalidate(sampleBuffer);//销毁
            CFRelease(sampleBuffer); //释放
        }
    }
    
    if (reader.status == AVAssetReaderStatusCompleted) {
        // 读取结束...
        if(callBlk) {
            callBlk(data);
        }
    } else {
        NSLog(@"获取音频数据失败");
    }
}

//削减音频、获取采样值
/**
 得到转化后的NSData数据之后，因为获得的data包含成千上万的byte，如果对data的每一个byte进行处理，
 会大大影响手机的处理速度，而且没有这个必要，因此需要对data进行削减，以减少处理的数量，达到性能上的提升。
 具体的削减的方式为：将data按照一定的比例缩小成一个一个的小的data包，将每一个data包中的最大值提取出来存
 入到数组集合中。经过这样的处理，数组中存放的就是成倍削减后的data了。看代码：
 */
//缩减音频 (size为将要绘制波纹的view的尺寸，不需要请忽略)
- (void)cutAudioData:(CGSize)size WithBlock:(RecorderDataDone)callBlk {
    NSMutableArray *filteredSamplesMA = [[NSMutableArray alloc]init];
    [self getRecorderDataFromURL:self.url WithBlock:^(id data) {
        NSData *audioData = [[NSMutableData alloc] initWithData:data];
        NSUInteger sampleCount = audioData.length / sizeof(SInt16);//计算所有数据个数
        NSUInteger binSize = sampleCount / (size.width * 0.1); //将数据分割为一个个小包
        
        SInt16 *bytes = (SInt16 *)audioData.bytes; //总的数据个数
        SInt16 maxSample = 0; //sint16两个字节的空间
        //以binSize为一个样本。每个样本中取一个最大数。也就是在固定范围取一个最大的数据保存，达到缩减目的
        for (NSUInteger i= 0; i < sampleCount; i += binSize) {
            //在sampleCount（所有数据）个数据中抽样，抽样方法为在binSize个数据为一个样本，在样本中选取一个数据
            SInt16 sampleBin[binSize];
            for (NSUInteger j = 0; j < binSize; j++) {//先将每次抽样样本的binSize个数据遍历出来
                sampleBin[j] = CFSwapInt16LittleToHost(bytes[i + j]);
            }
            
            //选取样本数据中最大的一个数据
            SInt16 value = [self maxValueInArray:sampleBin ofSize:binSize];
            
            //保存数据
            [filteredSamplesMA addObject:@(value)];
            //将所有数据中的最大数据保存，作为一个参考。可以根据情况对所有数据进行“缩放”
            if (value > maxSample) {
                maxSample = value;
            }
        }
        
        //计算比例因子
        CGFloat scaleFactor = (size.height * 0.5)/maxSample;
        //对所有数据进行“缩放”
        for (NSUInteger i = 0; i < filteredSamplesMA.count; i++) {
            filteredSamplesMA[i] = @([filteredSamplesMA[i] integerValue] * scaleFactor);
        }
        if(callBlk) {
            callBlk(filteredSamplesMA);
        }
    }];
}

//比较大小的方法，返回最大值
- (SInt16)maxValueInArray:(SInt16[])values ofSize:(NSUInteger)size {
    SInt16 maxvalue = 0;
    for (int i = 0; i < size; i++) {
        if (abs(values[i] > maxvalue)) {
            maxvalue = abs(values[i]);
        }
    }
    return maxvalue;
}

@end
